import requests
import json
import hashlib
import random
import re
from datetime import datetime, timedelta, time
from distutils.util import strtobool
from sqlalchemy.orm import Session
from core.response import resp_500
from core.logger import logger

from app.notice_center.handlers.nc_log_alarm_record_handler import NCLogAlarmRecordHandler
from app.notice_center.models.nc_log_alarm_record import NCLogAlarmRecord
from app.notice_center.models.nc_log_alarm_notice import NCLogAlarmNotice
from app.notice_center.models.nc_log_alarm_classify import NCLogAlarmClassify

from app.manage_notice.models.manage_notice_team import ManageNoticeTeam
from app.manage_notice.models.manage_notice_department import ManageNoticeDepartment
from common.utils import CommonTimeUtils
from common.notice_tools import NoticeTools

from setting import config


class NCLogAlarmHandler():
    """ NCLogAlarm  Handler
    """
    def __init__(self):
        self.nc_log_alarm_record_handler = NCLogAlarmRecordHandler()
        self.notice_tools = NoticeTools()



    def check_alert_notice_maintain(self, notice_strategy):
        # 没有维护时间
        if not notice_strategy["maintain_begin"] or not notice_strategy["maintain_end"]:
            logger.info(f"maintain is null")
            return True

        # 检查是否在维护时间内
        maintain_begin = CommonTimeUtils.strptime(notice_strategy["maintain_begin"])
        maintain_end = CommonTimeUtils.strptime(notice_strategy["maintain_end"])
        if CommonTimeUtils.check_between_datetime(maintain_begin, maintain_end) :
                logger.info(f"log Alarm datetime in maintain")
                return False

        logger.info(f"not in maintain")
        return True




    def check_alert_notice(self, db: Session, notice_strategy: dict, record_info: dict):
        # 告警级别为nothing或没有通知人，则不告警
        if notice_strategy["level"] == "nothing":
            logger.info(f"log alarm level is nothing")
            return False
        
        # 是否在告警规则的维护时间
        if not self.check_alert_notice_maintain(notice_strategy):
            logger.info(f"log alarm in maintain")
            return False
        
        # 检查全局告警屏蔽的配置，如果设置了BlockGlobalAlarm告警记录，且state值为0，则屏蔽
        global_notice_strategy = NCLogAlarmNotice.get_object_info_for_block_global(db)
        if global_notice_strategy and not self.check_alert_notice_maintain(global_notice_strategy):
            logger.info(f"block global alarm state in maintain")
            return False

        # 可以通知
        logger.info(f"log alarm can be notice")
        return True


    def is_upgrade_to_department(self, notice_strategy: dict, record_info: dict):
        # 告警级别为info或者warning，则不升级
        if notice_strategy["level"] in ["info","warning"]:
            return False
        
        # 告警级别为major，第四次再次告警时（20分钟）及以后，则升级到部门领导
        if notice_strategy["level"] == "major" and record_info["notice_count"] > 3:
            return True
        
        # 告警级别为critical，第二次再次告警（10分钟）及以后，则升级到部门领导
        if notice_strategy["level"] == "critical" and record_info["notice_count"] >1:
            return True
            
            
        return False

    def alarm_upgrade_to_department(self, db: Session, notice_strategy: dict, record_info: dict):
        """
        告警升级到部门领导
        """
        notice_teams_uuid = notice_strategy["notice_teams_uuid"]

        if self.is_upgrade_to_department(notice_strategy, record_info):
            # 告警通知人升级
            department_name = notice_strategy["department"]
            department_object = ManageNoticeDepartment.get_object_by_name(db, department_name)
            if not department_object:
                logger.error(f"get department by name:{department_name} failed")
                return notice_teams_uuid
            
            # 添加部门负责人
            department_info = ManageNoticeDepartment.get_object_info_by_name(db, department_name)
            notice_teams_uuid.extend(department_info["notice_teams_uuid"])
            notice_teams_uuid = list(set(notice_teams_uuid))

            # 通知人有变化，同步到数据
            record_info["notice_teams_uuid"] = notice_teams_uuid
            result = NCLogAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"update alarm record:notice_teams_uuid to database failed")

            logger.info(f"alarm upgrade to department success")
        return notice_teams_uuid




    def get_notice_info(self, db: Session, notice_strategy: dict, record_info: dict):
        """
        获取通知信息
        """
        notice_info = {
            "sms": [],
            "voice": [],
            "mailboxs": [],
            "chat_users": []
        }
        # 告警升级到部门领导
        notice_teams_uuid = self.alarm_upgrade_to_department(db, notice_strategy, record_info)
        
        # 获取通知人信息
        notice_users = []
        for notice_team_uuid in notice_teams_uuid:
            if not ManageNoticeTeam.get_object_by_uuid(db, notice_team_uuid):
                logger.error(f"get notice team failed, notice_team_uuid:{notice_team_uuid} not exist")
                continue

            notice_team_info = ManageNoticeTeam.get_object_info_by_uuid(db, notice_team_uuid)
            notice_users.extend(notice_team_info["users"])


        # 重组返回数据结构
        for user_info in notice_users:
            chat_user = user_info["mailbox"].split("@")[0]
            if chat_user not in notice_info["chat_users"]:
                notice_info["chat_users"].append(chat_user)
                notice_info["sms"].append(user_info["telephone"])
                notice_info["voice"].append({"username": chat_user, "mobile": user_info["telephone"]})
                notice_info["mailboxs"].append(user_info["mailbox"])

        logger.info(f"get notice info success")
        return notice_info



    def send_notice_wechat(self, notice_msg_card: str, notice_msg_markdown: str, notice_strategy: dict):
        """
        根据企业微信群的配置，将通知信息发送到群
        """
        notice_center_log_group = config.NOTICE_CENTER_LOG_GROUP.dict()
        
        if notice_strategy["server_type"] not in notice_center_log_group:
            logger.error(f"server_type:{notice_strategy['server_type']} not configured wechat")
            return False

        for group_config in notice_center_log_group[notice_strategy["server_type"]]:
            if group_config["classify"] == "card":
                # 发送企业微信小程序的卡片信息，在小程序那边生成工单，
                notice_msg_card["chat_id"] = group_config["group_id"]
                result = self.notice_tools.send_wechat_card(notice_msg_card)
                if not result:
                    logger.error(f"send wechat card failed")
                else:
                    logger.info(f"send wechat card success")
                
                continue
            
            if group_config["classify"] == "bot":
                # 发送到大群
                result = self.notice_tools.send_wechatbot(group_config["group_id"], notice_msg_markdown)
                if not result:
                    logger.error(f"send wechat bot failed")
                else:
                    logger.info(f"send wechat bot success")
                    
                continue
            
        return True
        




    def send_notice(self, db: Session, notice_strategy, notice_info, record_info):
        notice_msg = self.notice_tools.log_alarm_message(notice_strategy, notice_info, record_info)
        notice_msg_markdown = self.notice_tools.log_alarm_message_markdown(notice_strategy, notice_info, record_info)
        notice_msg_card = self.notice_tools.log_alarm_message_card(notice_strategy, notice_info, record_info)

        # 告警级别升级
        if notice_strategy["level"] in ["warning"] and record_info["notice_count"] >= 3:
            notice_strategy["level"] = "major"

        # 发送企业微信到个人，info（通知告警）级别以上
        result = self.notice_tools.send_wechat(notice_info["chat_users"], notice_msg_markdown, "markdown")
        if not result:
            logger.error(f"send wechat failed, chat_users=>{notice_info['chat_users']}")
        else:
            logger.info(f"send wechat success, chat_users=>{notice_info['chat_users']}")

       
        # 发送到企业微信群, warning（一般告警）级别以上
        if notice_strategy["level"] in ["warning", "major", "critical"]:
            result = self.send_notice_wechat(notice_msg_card, notice_msg_markdown, notice_strategy)
            if not result:
                logger.error(f"send wechat group failed")
            else:
                logger.info(f"send wechat group success")


        # 发送短信告警，warning（一般告警）级别以上， 并且不是工作时间才发送（节省费用）
        if notice_strategy["level"] in ["warning", "major", "critical"] and not CommonTimeUtils.check_now_is_work_time():
            result = self.notice_tools.send_sms(notice_info["sms"], notice_msg)
            if not result:
                logger.error(f"send sms failed, sms=>{notice_info['sms']}")
            else:
                logger.info(f"send sms success, sms=>{notice_info['sms']}")


        # 发送电话告警，major级别以上，只有告警时才打电话
        if notice_strategy["level"] in ["major", "critical"]:
            result = self.notice_tools.send_voice(notice_info["voice"])
            if not result:
                logger.error(f"send voice failed, voice=>{notice_info['voice']}")
            else:
                logger.info(f"send voice success, voice=>{notice_info['voice']}")

        # 更新通知次数
        record_info["notice_count"] += 1
        result = NCLogAlarmRecord.update(db, record_info)
        if not result:
            logger.error(f"update log alarm record: notice_count failed")
            return False

        logger.info(f"send notice all success =>\n{notice_msg}")
        return True



    def get_notice_strategy(self, db: Session, alert_info: dict):
        """
        获取服务告警通知的策略
        默认的
        服务类型: app_server
        告警级别: warning
        通知组：生产运维组 or 组id为1
        部门名称：运维
        """
        # 获取服务告警通知的策略
        notice_strategy_object = NCLogAlarmNotice.get_object_by_name(db, alert_info["service_name"])
        if not notice_strategy_object:
            # 没有配置，则设置默认值
            notice_strategy = {
                "name": alert_info["service_name"],
                "server_type": alert_info["log_type"],
                "comment": ""
            }
            logger.warning(f"server:{alert_info['service_name']} notice info is not configured")
        else:
            notice_strategy = NCLogAlarmNotice.get_object_info_by_name(db, alert_info["service_name"])
           
        # 检查告警级别，默认值为 warning
        if notice_strategy.get("level", "") not in ["critical", "major", "warning", "info", "nothing"]:
            notice_strategy["level"] = config.NOTICE_CENTER_LOG_LEVEL
            logger.warning(f"set default level:{notice_strategy['level']}")
            
        
        # 检查通知小组，默认值为：生产运维组或者小组id:1
        if "notice_teams_uuid" not in notice_strategy:
            notice_team_info = ManageNoticeTeam.get_object_info_by_name(db, config.NOTICE_CENTER_LOG_TEAM)
            if not notice_team_info:
                notice_strategy["notice_teams_uuid"] = [1]
                logger.warning(f"get notice team:{config.NOTICE_CENTER_LOG_TEAM} failed, set default notice team uuid:[1]")
            else:
                notice_strategy["notice_teams_uuid"] = [notice_team_info["uuid"]]
                logger.warning(f"set default notice team:{config.NOTICE_CENTER_LOG_TEAM} uuid:[{notice_team_info['uuid']}]")
            
        # 检查通知维护时间，默认为：None
        if not notice_strategy.get("maintain_begin", None) or not notice_strategy.get("maintain_end", None):
            notice_strategy["maintain_begin"] = None
            notice_strategy["maintain_end"] = None
            logger.warning(f"set default maintain_begin:None, maintain_end:None")
                
        # 检查服务的部门信息，默认为：运维
        if not notice_strategy.get("department", ""):
            notice_strategy["department"] = config.NOTICE_CENTER_LOG_DEPARTMENT
            logger.warning(f"set default department:{notice_strategy['department']}")
            

            
        return notice_strategy





    def alert_to_notice(self, db: Session, notice_strategy: dict, record_info: dict):
        # 检查是否需要通知
        if not self.check_alert_notice(db, notice_strategy, record_info):
            logger.info(f"log alarm not to notice ")
            return True

        # 获取通知人的信息
        notice_info = self.get_notice_info(db, notice_strategy, record_info)
        if not notice_info["chat_users"]:
            logger.error(f"get notice info is None")
            return False

        # 发送告警信息
        result = self.send_notice(db, notice_strategy, notice_info, record_info)
        if not result:
            logger.info(f"send notice failed")
            return False

        logger.info(f"alert to notice success")
        return True




    def get_classify(self, db: Session, alert_info: dict):
        """
        根据分类的关键字，匹配出分类名称
        """
        classify_infos = NCLogAlarmClassify.get_keywords_for_classify(db)
        for classify_info in classify_infos:
            # 防止||匹配所有的
            pattern = classify_info["keywords"].replace("||","|") 
            if re.search(pattern, alert_info["error_message"]):
                return classify_info["name"]
            
        return "未知分类"




    def add_alert_to_database(self, db: Session, alert_info: dict, notice_strategy: dict):
        """
        新增一条告警记录
        """
        
        # 告警信息的次数
        alert_info["alarm_number"] = 1
        
        # 生成告警事件指纹
        starts_at = datetime.now()
        fingerprint_msg = alert_info["service_name"] + str(starts_at.timestamp())
        fingerprint_md5 = hashlib.md5(fingerprint_msg.encode("utf-8"))
        fingerprint = fingerprint_md5.hexdigest()
        
        # 在数据库中新建一条记录
        data = {
            "name": alert_info["service_name"],
            "server_type": notice_strategy["server_type"],
            "classify": self.get_classify(db, alert_info),
            "alarm_state": "firing",
            "alarm_count": 1,
            "alarm_infos": [alert_info],
            "notice_count": 0,
            "notice_teams_uuid": notice_strategy["notice_teams_uuid"],
            "fingerprint": fingerprint,
            "starts_at": starts_at,
            "ends_at": None,
            "comment": ""
        }
        record_info = NCLogAlarmRecord.add(db, data)
        if not record_info:
            logger.error(f"log alarm record add to database failed")
            return False
        record_info = record_info.object_to_dict(db)
        
        return record_info




    def alert_to_database(self, db: Session, alert_info: dict, notice_strategy: dict):
        """
        告警信息写入数据库
        """
        record_info = NCLogAlarmRecord.get_object_info_at_firing(db, alert_info["service_name"])
        if not record_info:
            # 服务没有,增加告警的记录
            result = self.add_alert_to_database(db, alert_info, notice_strategy)
            if not result:
                logger.error(f"add alert to database failed")
                return False
            
            record_info = result
            logger.info(f"log alarm record add to database success")
        else:
            # 存在告警状态的记录， 更新状态
            alert_info["alarm_number"] = len(record_info["alarm_infos"]) + 1
            record_info["alarm_count"] += 1
            if not record_info["name"].endswith("LogWatchDog"):
                record_info["alarm_infos"].append(alert_info)
            record_info.pop("update_at")
            result = NCLogAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"log alarm record update to database failed")
                return False
            logger.info(f"log alarm record update to database success")

        return record_info



    def update_alerts(self, db: Session, alert_type: str, alert_infos: dict):
        """
        更新告警记录
        """
        # 在新的告警记录中，插入一条watchdog记录
        LogWatchDog = {
            "service_name": f"{alert_type}LogWatchDog",
            "log_type": "application",
            "error_count": 0,
            "cst_begin_time": "",
            "cst_end_time": "",
            "alarm_threshold": 0,
            "error_message": "功能验证, 告警请忽略",
            "app_url": "http://xxxxx/"
        }
        alert_infos.append(LogWatchDog)
        
        # 更新告警状态为firing 的告警记录
        now_time = datetime.now()
        now_timestamp = now_time.timestamp()
        record_infos = NCLogAlarmRecord.get_object_info_all_firing(db)
        for record_info in record_infos:
            if record_info["name"].endswith("LogWatchDog"):
                continue
            
            firing_timestamp = datetime.timestamp(CommonTimeUtils.strptime(record_info["update_at"]) + timedelta(minutes=20))
            if firing_timestamp < now_timestamp:
                # 如果服务有20分钟没有告警，则认为务服务已恢复
                # 更新告警记录为恢复状态
                record_info.pop("update_at")
                record_info["alarm_state"] = "resolved"
                record_info["ends_at"] = now_time
                result = NCLogAlarmRecord.update(db, record_info)
                if not result:
                    logger.error(f"log alarm record info to resolved failed")
                    return False
                
        return True



    def handle_alerts(self, db: Session, alert_info: dict):
        try:
            alert_info["error_message"] = alert_info["error_message"][:400]
            logger.info(f"收到日志告警信息=>{alert_info}")
            
            # 获取服务告警通知的策略
            notice_strategy = self.get_notice_strategy(db, alert_info)
        
            # 更新数据库中的记录信息
            record_info = self.alert_to_database(db, alert_info, notice_strategy)
            if not record_info:
                logger.error(f"log alarm to database failed, record_info=>{record_info}")
                return False

            # 发送告警信息
            result = self.alert_to_notice(db, notice_strategy, record_info)
            if not result:
                logger.error(f"log alarm to notice failed, record_info=>{record_info}")
                return False

            logger.info(f"处理完成:{alert_info}")
        except Exception as e:
            logger.error(f"处理失败 => {e}")
            return False

        return True




    def post_log_alarm(self, db: Session, alert_type: str, alert_infos: dict):
        try:
            # 标记过程是否失败，如果有失败的情况通知到群
            is_failed = False
            
            # 更新数据库中的告警记录状态
            result = self.update_alerts(db, alert_type, alert_infos)
            if not result:
                logger.error("update alerts failed")
                is_failed = True

            # 遍历处理每条日志告警
            for alert_info in alert_infos:
                result = self.handle_alerts(db, alert_info)
                if not result:
                    is_failed = True
                    logger.error("handle log alarm failed")
                    
            if is_failed:
                # 处理失败，发送信息到企业群
                notice_msg = f"{config.NOTICE_CENTER_LOG_NAME}：处理日志告警信息失败，请及时查看"
                result = self.notice_tools.send_wechat_group(notice_msg)
                if not result:
                    logger.error(f"processing_failure_message send to wechat_group failed")
                return False
        except Exception as e:
            message = f"post log alarm error"
            logger.exception(f"{message} => {e}")
            return False
        
        logger.info("handle all log alarm success")
        return True
        
            
    








def main():
    from common.database import SessionLocal
    db = SessionLocal()
    nc_log_alarm_handler = NCLogAlarmHandler()
    
    alert_info = [
        {
            "service_name": "test-service",
            "cst_begin_time": "16:00:00",
            "cst_end_time": "16:05:00",
            "error_count": 58,
            "character_log": """[ERROR] [traffic-core-service] 2023-05-24 10:57:42.463 [http-nio-8080-exec-8] [] [] [TID: N/A] com.welab.tb.common.core.config.exception.GlobalExceptionHandler$$EnhancerBySpringCGLIB$$ad2e06b1.methodArgumentNotValidHandler --> java.io.IOException: Broken pipe
org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe
        at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:353)
        at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:783)
        at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:688)
        at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:388)
        at org.apache.catalina.connector.OutputBuffer.write(OutputBuffer.java:366)
        at org.apache.catalina.connector.CoyoteOutputStream.write(CoyoteOutputStream.java:96)
        at org.springframework.util.StreamUtils$NonClosingOutputStream.write(StreamUtils.java:287)
        at com.fasterxml.jackson.core.json.UTF8JsonGenerator._flushBuffer(UTF8JsonGenerator.java:2177)
        at com.fasterxml.jackson.core.json.UTF8JsonGenerator._writeStringSegments(UTF8JsonGenerator.java:1319)
        at com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeString(UTF8JsonGenerator.java:517)
        """,
            "app_url": "https://k.wolaidai.com/28G"
        }
    ]
    # alert_info = []
    nc_log_alarm_handler.post_log_alarm(db, alert_info)

    db.close()
    

if __name__ == "__main__":
    main()





